iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0

Unit Test 應用於 Web APIs-前言

現今大多數的軟體工程都是以網路工程為主,那網路工程中又以 Web API 為單位做為開發的基石;因此,今天我們了解如何撰寫 Web API 的單元測試,來提升軟體開發的準確性。那首先,我們要介紹 Web API (假設部分在閱覽的讀者沒有接觸網路工程),Web API —— Web Application Programming Interface,白話文就是網路應用程式與網路應用程式之間溝通的橋樑,那 Web API 可依據使用者開發決定提供 XML、Json、GeoJson、File...等,以下幾篇為我覺得不錯的 Web API 參考文章,供各位參考:

那通常檢測 Web API,都是在檢測相對應的 Controller(以瀏覽器來看是一個對應的 Url),因應不同的 Controller 會有不同的服務如 HttpGet、HttpPost...等,那這次的範例是以 HttpPost 為例。


看程式碼說故事 (Web API-1)

於是乎,我們就開始撰寫 Web API 專案,那這次範例所採用的框架是 N-Tiers 框架,主要流程是

Controller -> IService (實作用 Service) -> Model

而今天的情境是有使用者登錄商品的資料,登錄完資料後,我們要登記 Log 並且給予這個資料一組 Unique GUID,程式碼如下:

Controller 層:

namespace Products.Controllers
{
    [Route("api/[controller]")]
    public class ProductsController : Controller
    {
        private readonly ILogger Logger;
        private readonly IProductService ProductService;

        public ProductsController(ILogger inLogger, IProductService inProductService)
        {
            Logger = inLogger;
            ProductService = inProductService;
        }

        [HttpPost]
        public string Post(Product product)
        {
            Logger.Log("High", $"Adding a products with an id {product.ProductName}");

            var productGuid = ProductService.SaveProduct(product);

            return productGuid;
        }
    }
}

IService 層(服務介面層):

namespace Products.IService
{
    // 登記 Log 的服務
    public interface ILogger
    {
        public void Log(string level, string message);
    }

    // 產品 Product 的服務
    public interface IProductService
    {
        public string SaveProduct(Product product);
    }
}

Service 層(服務實作層):

namespace Products.Service
{
    // 登記 Log 的服務
    public class Logger : ILogger
    {
        public void Log(string level, string message)
        {
            // 撰寫你要的功能,並非測試重點,所以不詳細列述
        }
    }

    // 產品 Product 的服務
    public class ProductService : IProductService
    {
        public string SaveProduct(Product product)
        {
            // 撰寫你要的功能,並非測試重點,所以不詳細列述
        }
    }
}

Model 層(資料模型):

namespace Products.Models
{
    public class Product
    {
        // 產品序號
        public string ProductId;

        // 產品名稱
        public string ProductName;

        // 產品現貨數量
        public int QuantityAvailable;
    }
}

看程式碼說故事 (Web API-2)

所以,我們要檢測 HttpPost 是否正常運行,可思考幾個關注點:

  1. GUID 回傳值
  2. SaveProduct 是否呼叫與次數
  3. Log 是否呼叫與次數

列好了關注點之後,就可以開始撰寫測試,如下:

[TestFixture]
public class ProductsControllerTests
{
    private ILogger Logger;
    private IProductService ProductService;

    private ProductsController ProductsController;

    [SetUp]
    public void SetUp()
    {
        Logger = Substitute.For<ILogger>();
        ProductService = Substitute.For<IProductService>();

        ProductsController = new ProductsController(Logger, ProductService);
    }

    [Test]
    public void DemoGuidTest()
    {
        // Arrange
        var guid = "af95003e-b31c-4904-bfe8-c315c1d2b805";
        var product = new Product { ProductId = "1", ProductName = "Oven", QuantityAvailable = 3 };

        Logger.Log(default, default);
        ProductService.SaveProduct(product).Returns(guid);

        // Act
        var result = ProductsController.Post(product);

        // Assert
        Assert.AreEqual(result, guid);
    }

    [Test]
    public void DemoSaveProductReceiveTest()
    {
        // Arrange
        var guid = "af95003e-b31c-4904-bfe8-c315c1d2b805";
        var product = new Product { ProductId = "1", ProductName = "Oven", QuantityAvailable = 3 };

        Logger.Log(default, default);
        ProductService.SaveProduct(product).Returns(guid);

        // Act
        var result = ProductsController.Post(product);

        // Assert
        ProductService.Received(1).SaveProduct(product);
    }

    [Test]
    public void DemoLogReceiveTest()
    {
        // Arrange
        var guid = "af95003e-b31c-4904-bfe8-c315c1d2b805";
        var product = new Product { ProductId = "1", ProductName = "Oven", QuantityAvailable = 3 };

        Logger.Log(default, default);
        ProductService.SaveProduct(product).Returns(guid);

        // Act
        var result = ProductsController.Post(product);

        // Assert
        Logger.Received(1).Log(default, default);
    }
}

若實務上,這三個測試都成功,則代表的確有呼叫到 Log 與 SaveProduct 各一次,並且有成功回傳 GUID,這樣就算是個良好的單元測試組。


文章故事情境參考來源:https://www.michalbialecki.com/2019/01/03/writing-unit-tests-with-nunit-and-nsubstitute/


上一篇
Day 20-重構 (Refactoring) 與接縫 (Seam) - 2 (核心技術-12)
下一篇
Day 22-Unit Test 應用於 DateTime-1 (情境及應用-2)
系列文
單元測試從入門到進階之路 (以 C# NUnit 3 X NSubstitute 為例)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言